﻿#region Copyright & licence

// This file is part of Refix.
// 
// Refix is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
// 
// Refix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with Refix.  If not, see <http://www.gnu.org/licenses/>.
// 
// Copyright (C) 2010-11 David Musgrove and others.

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;

using Refix.Core.Options;
using Refix.Core.Properties;
using Refix.Core.Repository;

namespace Refix.Core.Commands
{
    public class Register : Command
    {
        protected override string HelpText
        {
            get { return ResourceDispenser.GetString("REGISTER_HELP"); }
        }

        public override bool Validate()
        {
            if (!base.Validate())
            {
                return false;
            }
            if (Options.Arguments.Count == 0)
            {
                OutputWriter.WriteLine(OutputVerbosity.Quiet, ResourceDispenser.GetString("REGISTER_NO_ARGUMENTS"));
                return false;
            }
            return true;
        }

        public override bool Execute()
        {
            IEnumerable<FileInfo> assemblyFiles = GetMatchingAssemblyFiles();
            if (assemblyFiles == null)
            {
                return false;
            }
            IEnumerable<Assembly> assemblies = LoadAssemblies(assemblyFiles);
            if (assemblies == null)
            {
                return false;
            }
            LocalRepository localRepository = GetLocalRepository();
            localRepository.Offline = Options.Options.Any(o => o is Offline);
            IEnumerable<FileInfo> extraFiles = GetMatchingExtraFiles();
            string clrVersion = null;
            var clrVersionOption = (ClrVersion)Options.Options.FirstOrDefault(o => o is ClrVersion);
            if (clrVersionOption != null)
            {
                clrVersion = clrVersionOption.Version;
            }
            foreach (Assembly assembly in assemblies)
            {
                try
                {   
                    IEnumerable<string> externals = (from module in assembly.GetModules()
                                                     from type in module.GetTypes()
                                                     from methodInfo in type.GetMethods()
                                                     from DllImportAttribute customAttribute in
                                                         methodInfo.GetCustomAttributes(typeof(DllImportAttribute), true)
                                                     select customAttribute.Value).Distinct();
                    bool existing = !CreateComponentAndVersion(assembly, clrVersion, externals);
                    string destinationFolder = localRepository.GetAssemblyVersionFolder(assembly);
                    if (!clrVersion.IsNullOrEmpty())
                    {
                        destinationFolder = Path.Combine(destinationFolder, clrVersion);
                    }
                    foreach (var extraFile in extraFiles)
                    {
                        var destinationFile = Path.Combine(destinationFolder, extraFile.Name);
                        if (existing && !File.Exists(destinationFile))
                        {
                            using (OutputWriter.Highlight(ConsoleColor.Yellow))
                            {
                                OutputWriter.Write(ResourceDispenser.GetString("LOCAL_REPOSITORY_WARNING_ADDING_ADDITIONAL_FILES_TO_REG_VERSION"), assembly.GetName().Name, assembly.GetName().Version, extraFile.Name);
                            }
                        }
                        extraFile.CopyTo(destinationFile);
                    }
                }
                catch (Exception) {}
            }
            return true;
        }

        private bool CreateComponentAndVersion(Assembly assembly, string clrVersion, IEnumerable<string> externals)
        {
            LocalRepository localRepository = GetLocalRepository();
            if (!localRepository.RegisterAssembly(assembly, clrVersion))
            {
                return false;
            }
            string name = assembly.GetName().Name;
            string versionNumber = assembly.GetName().Version.ToString();
            OutputWriter.WriteLine(ResourceDispenser.GetString("REGISTER_COMPONENT_ADDED"), name, versionNumber);
            localRepository.PutExternalReferences(assembly, clrVersion, externals);
            return true;
        }

        static private IEnumerable<Assembly> LoadAssemblies(IEnumerable<FileInfo> assemblyFiles)
        {
            var assemblies = new List<Assembly>();
            foreach (FileInfo assemblyFile in assemblyFiles)
            {
                Assembly assembly;
                try
                {
                    try
                    {
                        assembly = Assembly.LoadFrom(assemblyFile.FullName);
                        if (!assemblies.Any(a => a.FullName == assembly.FullName))
                        {
                            assemblies.Add(assembly);
                        }
                    }
                    catch (BadImageFormatException)
                    {
                        assembly = Assembly.ReflectionOnlyLoadFrom(assemblyFile.FullName);
                        if (!assemblies.Any(a => a.FullName == assembly.FullName))
                        {
                            assemblies.Add(assembly);
                        }
                    }
                }
                catch (Exception)
                {
                    OutputWriter.WriteLine(OutputVerbosity.Quiet,
                                       ResourceDispenser.GetString("REGISTER_FILE_NOT_AN_ASSEMBLY"), assemblyFile.Name);
                    return null;
                }
            }
            return assemblies;
        }

        private IEnumerable<FileInfo> GetMatchingExtraFiles()
        {
            string workingDirectory = Directory.GetCurrentDirectory();
            var extraFiles = new List<FileInfo>();
            foreach (Extra extra in Options.Options.Where(o => o is Extra))
            {
                string path = extra.FilePattern;
                if (!Path.IsPathRooted(path))
                {
                    path = Path.Combine(workingDirectory, path);
                }
                FileInfo[] fileInfos = new DirectoryInfo(Path.GetDirectoryName(path)).GetFiles(Path.GetFileName(path));
                extraFiles.AddRange(fileInfos);
            }
            return extraFiles;
        }

        private IEnumerable<FileInfo> GetMatchingAssemblyFiles()
        {
            string workingDirectory = Directory.GetCurrentDirectory();
            var assemblyFiles = new List<FileInfo>();
            foreach (string pattern in Options.Arguments)
            {
                string path = pattern;
                if (!Path.IsPathRooted(path))
                {
                    path = Path.Combine(workingDirectory, path);
                }
                FileInfo[] fileInfos = new DirectoryInfo(Path.GetDirectoryName(path)).GetFiles(Path.GetFileName(path));
                if (fileInfos.Length == 0)
                {
                    OutputWriter.WriteLine(OutputVerbosity.Quiet, ResourceDispenser.GetString("REGISTER_NO_MATCHES"),
                                       pattern);
                    return null;
                }
                assemblyFiles.AddRange(fileInfos);
            }
            return assemblyFiles;
        }
    }
}